from collections import Counter, defaultdict
import os

ENCODING = 'utf8'

# FPSZ = 256 # blkparse文件中, 一个hash的长度(目前只解析为字符串)
NEWFPSZ = 32 # hitsztrace文件中, 一个hash的长度(目前只解析为字符串)

def blkparse_to_hitsztrace(srcPath: str, dstPath: str):
    with open(srcPath, 'r', encoding=ENCODING) as fsrc, open(dstPath, 'w', encoding=ENCODING) as fdst:
        shitCnt = 0
        for line in fsrc:
            elems = line.strip().split(' ')
            lbn, blk, rw, hash_line = int(elems[3]), int(elems[4]), elems[5], elems[8]

            # hitsztrace不需要Read
            if rw == 'R':
                continue

            # 检查hash长度是否正确, 防止文件错误  # 暂时不用 后面检查FPSZ时可以检查
            # if len(hash_line) != blk * FPSZ // 8:
            #     print(f"Error: {srcPath}")
            #     print(f"blk: {blk}, hash_len: {len(hash_line)}")
            #     return
            
            # 读写块数不为8的倍数直接跳过
            if blk % 8 != 0:
                shitCnt += 1
                continue

            # 判断大于16TB的情况
            if lbn > 2 ** (44 - 12) or lbn % 8 != 0:
                print(f"lbn too big Error: {srcPath}")
                print(f"lbn: {hex(lbn)}")
                return

            blkCnt = blk // 8
            FPSZ = len(hash_line) // blkCnt
            if FPSZ != 32 and FPSZ != 256:
                print(f"FPSZ Error: {srcPath}")
                print(f"FPSZ: {FPSZ}")
                return
            assert NEWFPSZ <= FPSZ

            if rw == 'W':
                for j in range(0, blkCnt):
                    fdst.write(f"0 0 {lbn + j * 8} {hash_line[j*FPSZ:j*FPSZ+FPSZ]}\n")

    print(srcPath, dstPath, shitCnt)

def reOrderHitsztrace(srcPath: str, dstPath: str, hotFirst=True):
    """
    将hitsztrace文件按照访问热度排序
    """
    assert srcPath.endswith('.hitsztrace')
    with open(srcPath, 'r', encoding=ENCODING) as fsrc, open(dstPath, 'w', encoding=ENCODING) as fdst:
        lbn2hash = defaultdict(list)

        # 创建lbn -> hashList的映射
        # 将所有hash放到lbn对应的list中
        for line in fsrc:
            elems = line.strip().split(' ')
            lbn, dataHash = int(elems[2]), elems[3]
            assert len(dataHash) == FPSZ

            lbn2hash[lbn].append(dataHash)

        # 将lbn2hash按照hashList长度排序
        # reverse=True表示降序, 从热到冷, 反之为从冷到热
        lbn2hash_sorted = sorted(lbn2hash.items(), key=lambda x: len(x[1]), reverse=hotFirst)

        # 将排序过的访问请求写入文件
        counter = Counter()
        for lbn, hash_list in lbn2hash_sorted:
            counter[len(hash_list)] += 1
            for dataHash in hash_list:
                fdst.write(f"0 0 {lbn} {dataHash}\n")
        print(counter)


def sliceBlkparse(path, blkCnt):
    cnt = 0
    dst = os.path.splitext(path)[0] + "_16GB.blkparse"
    with open(path, 'r', encoding=ENCODING) as f, open(dst, 'w', encoding=ENCODING) as fdst:
        for line in f:
            elems = line.strip().split(' ')
            blk, rw, hash_line = int(elems[4]), elems[5], elems[8]

            if rw == 'R':
                continue
            if blk % 8 != 0:
                shitCnt += 1
                continue
            
            cnt += blk // 8
            fdst.write(line)
            if cnt >= blkCnt:
                break

    print(path, dst, cnt)
    
    # 输出结果
    return f"{os.path.basename(path)}\n" + \
        str(cnt) + '\n'


SHA1_LEN = 40
def smartdedupTrace_to_hitsztrace(srcPath: str, fdst):
    """
    将smartdedup的trace转换为hitsztrace
    由于SmartDedup trace都很小, 需要组合起来, 故使用fdst直接追加写而不是使用原本的dstPath
    """
    lbnCnt = 0  # lbn计数器, 一直为8的整数倍
    lineCnt = 0
    lbnMap = {}
    with open(srcPath, 'r', encoding=ENCODING) as fsrc:
        for line in fsrc:
            elems = line.strip().split(',')

            # 先进行一些过滤
            if len(elems) != 8:
                # 不是写入操作, 不需要额外过滤读操作, 因为读操作没有哈希
                # https://visa.lab.asu.edu/traces/smartphone/README
                continue
            ts, filename, ino, size, offset, rType, prosess, fp = elems
            if size != "4096" or int(offset) % 4096 != 0:
                # 只要4096整数倍
                continue
            assert len(fp) == SHA1_LEN

            # 查找lbn
            lbn = lbnMap.get((ino, offset), None)
            if lbn is None:
                lbn = lbnCnt
                lbnMap[(ino, offset)] = lbn
                lbnCnt += 8
            # 写入目的地
            lineCnt += 1
            fdst.write(f"0 0 {lbn} {fp}\n")

    print(srcPath, lbnCnt//8, lineCnt)

def hitsztrace_dup(srcPath: str, dstPath: str):
    """
    将一个hitsztrace文件中的每个哈希加一
    """
    assert srcPath.endswith('.hitsztrace') and dstPath.endswith('.hitsztrace')
    with open(srcPath, 'r', encoding=ENCODING) as fsrc, open(dstPath, 'w', encoding=ENCODING) as fdst:

        # 创建lbn -> hashList的映射
        # 将所有hash放到lbn对应的list中
        for line in fsrc:
            elems = line.strip().split(' ')
            lbn, dataHash = elems[2], elems[3]
            assert len(dataHash) == 32

            # 写入目的地
            dataHash=hex(int(dataHash[0], 16) + 1)[-1] + dataHash[1:]
            fdst.write(f"0 0 {lbn} {dataHash}\n")

def hitsztrace_SliceUnique(srcPath: str, dstPath: str, uniqueGB):
    """
    切割hitsztrace文件, 使得非重复的数据量为uniqueGB
    """
    assert srcPath.endswith('.hitsztrace')
    hashCounter = Counter()
    uniqueBlk = uniqueGB * 1024 * 1024 // 4
    with open(srcPath, 'r', encoding=ENCODING) as fsrc, open(dstPath, 'w', encoding=ENCODING) as fdst:

        # 创建lbn -> hashList的映射
        # 将所有hash放到lbn对应的list中
        for line in fsrc:
            elems = line.strip().split(' ')
            lbn, dataHash = int(elems[2]), elems[3]
            assert len(dataHash) in [32, 40]

            fdst.write(line)

            hashCounter[dataHash] += 1
            if len(hashCounter) >= uniqueBlk:
                break
            
